/*
* Project manager: read, write, add, remove, ... and save
*/

'use strict';

const fs = require('fs');
const path = require('path');
const async = require('async');

var events = require('../events');
var utils = require('../utils');
const prjstorage = require('./prjstorage');
const DeviceType = require('../devices/device').DeviceType;

const version = '1.02';
var settings;                   // Application settings
var logger;                     // Application logger
var runtime;

var data = {};                  // Project data
var suposDevicesTagsObject = {};

/**
 * Init Project resource and update project
 * @param {*} _settings
 * @param {*} log
 */
function init(_settings, log, _runtime) {
    settings = _settings;
    logger = log;
    runtime = _runtime;

    // Init Project database
    return new Promise(function (resolve, reject) {
        prjstorage.init(settings, logger).then(result => {
            logger.info('project.prjstorage-init-successful!', true);
            if (result) {
                resolve();
            } else {
                prjstorage.setDefault().then(result => {
                    logger.info('project.prjstorage-set-default-successful!', true);
                    resolve();
                }).catch(function (err) {
                    logger.error(`project.prjstorage-set-default failed! ${err}`);
                    resolve();
                });
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage-failed-to-init! ${err}`);
            reject(err);
        });
    });
}

/**
 * Load project resource in a local data
 * Rea d all storaged sections and fill in local data
 */
function load(layoutId) {
    return new Promise(function (resolve, reject) {
        data = { devices: {}, hmi: { views: [] }, texts: [], alarms: [] };
        // load general data
        prjstorage.getSection(prjstorage.TableType.GENERAL, layoutId).then(result => {
            if (result.length !== 0 && result[0].name === layoutId) {
                const grows = JSON.parse(result[0].value);
                Object.keys(grows).forEach(function (key) {
                    if (key === ProjectDataCmdType.HmiLayout) {
                        data.hmi[key] = grows[key];
                    } else {
                        data[key] = grows[key];
                    }
                });
            }

            // load views
            prjstorage.getSection(prjstorage.TableType.VIEWS, undefined, layoutId).then(vrows => {
                for (var iv = 0; iv < vrows.length; iv++) {
                    data.hmi.views.push(JSON.parse(vrows[iv].value));
                }
                // load devices
                const layoutIds = layoutId ? [layoutId, '0', 'server'] : layoutId;
                prjstorage.getSection(prjstorage.TableType.DEVICES, undefined, layoutIds).then(drows => {
                    for (var id = 0; id < drows.length; id++) {
                        if (drows[id].name === 'server') {
                            data[drows[id].name] = JSON.parse(drows[id].value);
                        } else {
                            data.devices[drows[id].name] = JSON.parse(drows[id].value);
                        }
                    }
                    async.series([
                        // step 1 get texts
                        function (callback) {
                            getTexts(layoutId).then(texts => {
                                data.texts = texts;
                                callback();
                            }).catch(function (err) {
                                logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.TEXTS}' ${err}`);
                                callback(err);
                            });
                        },
                        // step 2 get alarms
                        function (callback) {
                            getAlarms(layoutId).then(alarms => {
                                data.alarms = alarms;
                                callback();
                            }).catch(function (err) {
                                logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.ALARMS}' ${err}`);
                                callback(err);
                            });
                        },
                        // step 3 get notifications
                        function (callback) {
                            getNotifications(layoutId).then(notifications => {
                                data.notifications = notifications;
                                callback();
                            }).catch(function (err) {
                                logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.NOTIFICATIONS}' ${err}`);
                                callback(err);
                            });
                        },
                        // step 4 get scripts
                        function (callback) {
                            getScripts(layoutId).then(scripts => {
                                data.scripts = scripts;
                                callback();
                            }).catch(function (err) {
                                logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.SCRIPTS}' ${err}`);
                                callback(err);
                            });
                        },
                        // step 5 get reports
                        function (callback) {
                            getReports(layoutId).then(reports => {
                                data.reports = reports;
                                callback();
                            }).catch(function (err) {
                                logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.REPORTS}' ${err}`);
                                callback(err);
                            });
                        },
                        // step 6 get MapsLocations
                        function (callback) {
                            getMapsLocations().then(locations => {
                                data.mapsLocations = locations;
                                callback();
                            }).catch(function (err) {
                                logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.LOCATIONS}' ${err}`);
                                callback(err);
                            });
                        }
                    ],
                    async function (err) {
                        if (err) {
                            reject(err);
                        } else {
                            await _mergeDefaultConfig();
                            resolve();
                        }
                    });
                }).catch(function (err) {
                    logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.DEVICES}' ${err}`);
                    reject(err);
                });
            }).catch(function (err) {
                logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.VIEWS}' ${err}`);
                reject(err);
            });
        }).catch(function (err) {
            logger.error(`project.prjstorage-failed-to-load! '${prjstorage.TableType.GENERAL}' ${err}`);
            reject(err);
        });
    });
}

function updateGeneralData(key, value) {
  const {
    layout,
    plugin,
    verison = "1.00",
    projectFile,
    charts,
    name,
    graphs,
  } = data || {};

  data[key] = value;

  return {
    layout,
    plugin,
    charts,
    graphs,
    verison,
    name,
    projectFile,
    [key]: value,
  };
}

/**
 * Save the value in project storage
 * First set the value in local data, then save in storage
 * @param {*} cmd
 * @param {*} data
 */
function setProjectData(cmd, value, layoutId) {
    return new Promise(function (resolve, reject) {
        try {
            var toremove = false;
            var section = { table: '', name: '', value: value, layoutId };
            if (cmd === ProjectDataCmdType.SetView) {
                section.table = prjstorage.TableType.VIEWS;
                section.name = value.id;
                setView(value);
            } else if (cmd === ProjectDataCmdType.DelView) {
                section.table = prjstorage.TableType.VIEWS;
                section.name = value.id;
                toremove = removeView(value);
            } else if (cmd === ProjectDataCmdType.HmiLayout) {
                section.table = prjstorage.TableType.GENERAL;
                section.name = cmd;
                setHmiLayout(value);
            } else if (cmd === ProjectDataCmdType.SetDevice) {
                section.table = prjstorage.TableType.DEVICES;
                section.name = value.id;
                section.layoutId = value.id === '0' ? '0' : layoutId;
                setDevice(value);
            }  else if (cmd === ProjectDataCmdType.SetDevices) {
                section = [];
                value.forEach((v) => {
                    section.push({
                        table: prjstorage.TableType.DEVICES,
                        name: v.id,
                        value: v,
                        layoutId: v.id === '0' ? '0' : layoutId
                    })
                    setDevice(v);
                });
            } else if (cmd === ProjectDataCmdType.UpdateDevice) {
                section.table = prjstorage.TableType.DEVICES;
                section.name = value.id;
                section.layoutId = value.id === '0' ? '0' : layoutId;
                updateDevice(value);
            } else if (cmd === ProjectDataCmdType.DelDevice) {
                section.table = prjstorage.TableType.DEVICES;
                section.name = value.id;
                toremove = removeDevice(value);
            } else if (cmd === ProjectDataCmdType.Charts) {
                section.table = prjstorage.TableType.GENERAL;
                section.name = cmd;
                setCharts(value);
            } else if (cmd === ProjectDataCmdType.Languages) {
                section.table = prjstorage.TableType.GENERAL;
                section.name = cmd;
                setLanguages(value);
            } else if (cmd === ProjectDataCmdType.ClientAccess) {
                section.table = prjstorage.TableType.GENERAL;
                section.name = cmd;
                setClientAccess(value);
            } else if (cmd === ProjectDataCmdType.Graphs) {
                section.table = prjstorage.TableType.GENERAL;
                section.name = layoutId;
                section.value = updateGeneralData(cmd, value);
                cmd === ProjectDataCmdType.Charts
                  ? setCharts(value)
                  : cmd === ProjectDataCmdType.Languages
                  ? setLanguages(value)
                  : setGraphs(value);
            } else if (cmd === ProjectDataCmdType.SetText) {
                section.table = prjstorage.TableType.TEXTS;
                section.name = value.id;
                setText(value);
            } else if (cmd === ProjectDataCmdType.DelText) {
                section.table = prjstorage.TableType.TEXTS;
                section.name = value.id;
                toremove = removeText(value);
            } else if (cmd === ProjectDataCmdType.SetAlarm) {
                section.table = prjstorage.TableType.ALARMS;
                section.name = value.name;
                setAlarm(value);
            } else if (cmd === ProjectDataCmdType.DelAlarm) {
                section.table = prjstorage.TableType.ALARMS;
                section.name = value.name;
                toremove = removeAlarm(value);
            } else if (cmd === ProjectDataCmdType.SetNotification) {
                section.table = prjstorage.TableType.NOTIFICATIONS;
                section.name = value.id;
                setNotification(value);
            } else if (cmd === ProjectDataCmdType.DelNotification) {
                section.table = prjstorage.TableType.NOTIFICATIONS;
                section.name = value.id;
                toremove = removeNotification(value);
            } else if (cmd === ProjectDataCmdType.SetScript) {
                section.table = prjstorage.TableType.SCRIPTS;
                section.name = value.id;
                setScript(value);
            } else if (cmd === ProjectDataCmdType.DelScript) {
                section.table = prjstorage.TableType.SCRIPTS;
                section.name = value.id;
                toremove = removeScript(value);
            } else if (cmd === ProjectDataCmdType.SetReport) {
                section.table = prjstorage.TableType.REPORTS;
                section.name = value.id;
                setReport(value);
            } else if (cmd === ProjectDataCmdType.DelReport) {
                section.table = prjstorage.TableType.REPORTS;
                section.name = value.id;
                toremove = removeReport(value);
            } else if (cmd === ProjectDataCmdType.SetMapsLocation) {
                section.table = prjstorage.TableType.LOCATIONS;
                section.name = value.id;
                setMapsLocation(value);
            } else if (cmd === ProjectDataCmdType.DelMapsLocation) {
                section.table = prjstorage.TableType.LOCATIONS;
                section.name = value.id;
                toremove = removeMapsLocation(value);
            } else {
                logger.error(`prjstorage.setdata failed! '${section.table}'`);
                reject('prjstorage.failed-to-setdata: Command not found!');
            }
            if (toremove) {
                prjstorage.deleteSection(section).then(result => {
                    resolve(true);
                }).catch(function (err) {
                    logger.error(`prjstorage.deletedata failed! '${section.table}'`);
                    reject(err);
                });
            } else if (Array.isArray(section)) {
                prjstorage.setSections(section).then(result => {
                    resolve(true);
                }).catch(function (err) {
                    logger.error(`prjstorage.setdata failed! '${section.table}'`);
                    reject(err);
                });
            } else {
                prjstorage.setSection(section).then(result => {
                    resolve(true);
                }).catch(function (err) {
                    logger.error(`prjstorage.setdata failed! '${section.table}'`);
                    reject(err);
                });
            }
        } catch (err) {
            reject(err);
        }
    });
}

/**
 * Set or add if not exist (check with view.id) the View in Project
 * @param {*} view
 */
function setView(view) {
    var pos = -1;
    for (var i = 0; i < data.hmi.views.length; i++) {
        if (data.hmi.views[i].id === view.id) {
            pos = i;
        }
    }
    if (pos >= 0) {
        data.hmi.views[pos] = view;
    } else {
        data.hmi.views.push(view);
    }
}

/**
 * Remove the View from Project
 * @param {*} view
 */
function removeView(view) {
    var pos = -1;
    for (var i = 0; i < data.hmi.views.length; i++) {
        if (data.hmi.views[i].id === view.id) {
            data.hmi.views.splice(i, 1);
            return true;
        }
    }
    return false;
}

/**
 * Set Device to local data
 * @param {*} device
 * @param {*} merge merge with exist (tags)
 */
function setDevice(device, merge) {
    if (merge && data.devices[device.id]) {
        device.enabled = data.devices[device.id].enabled;
        data.devices[device.id] = {...data.devices[device.id], ...device};
    } else {
        data.devices[device.id] = device;
    }
}

/**
 * update Device to local data
 * @param {*} device
 */
function updateDevice(device) {
    if (!data.devices[device.id]) {
        data.devices[device.id] = device;
    } else {
        data.devices[device.id] = {
            ...data.devices[device.id],
            ...device,
            tags: {
                ...(data.devices[device.id].tags || {}),
                ...(device.tags || {})
            }
        }
    }
}

/**
 * Remove Device from local data
 * @param {*} device
 */
function removeDevice(device) {
    delete data.devices[device.id];
    return true;
}

/**
 * Set HMI Layout to local data
 * @param {*} layout
 */
function setHmiLayout(layout) {
    data.hmi.layout = layout;
}

/**
 * Set Charts
 * @param {*} charts
 */
function setCharts(charts) {
    data.charts = charts;
}

/**
 * Set Graphs
 * @param {*} graphs
 */
 function setGraphs(graphs) {
    data.graphs = graphs;
}

/**
 * Set Languages
 * @param {*} languages
 */
function setLanguages(languages) {
    data.languages = languages;
}

/**
 * Set ClientAccess
 * @param {*} clientAccess
 */
function setClientAccess(clientAccess) {
    data.clientAccess = clientAccess;
}

/**
 * Set or add if not exist (check with taxt.name) the Text in Project
 * @param {*} text
 */
function setText(text) {
    if (!data.texts) {
        data.texts = [];
    }
    var pos = -1;
    for (var i = 0; i < data.texts.length; i++) {
        if (data.texts[i].id === text.id) {
            pos = i;
        }
    }
    if (pos >= 0) {
        data.texts[pos] = text;
    } else {
        data.texts.push(text);
    }
}

/**
 * Remove the Text from Project
 * @param {*} text
 */
function removeText(text) {
    if (data.texts) {
        for (var i = 0; i < data.texts.length; i++) {
            if (data.texts[i].id === text.id) {
                data.texts.splice(i, 1);
                return true;
            }
        }
    }
    return false;
}

/**
 * Set or add if not exist (check with alarm.name) the Alarm in Project
 * @param {*} alarm
 */
function setAlarm(alarm) {
    if (!data.alarms) {
        data.alarms = [];
    }
    var pos = -1;
    for (var i = 0; i < data.alarms.length; i++) {
        if (data.alarms[i].name === alarm.name) {
            pos = i;
        }
    }
    if (pos >= 0) {
        data.alarms[pos] = alarm;
    } else {
        data.alarms.push(alarm);
    }
}

/**
 * Remove the Alarm from Project
 * @param {*} alarm
 */
function removeAlarm(alarm) {
    if (data.alarms) {
        for (var i = 0; i < data.alarms.length; i++) {
            if (data.alarms[i].name === alarm.name) {
                data.alarms.splice(i, 1);
                return true;
            }
        }
    }
    return false;
}

/**
 * Set or add if not exist (check with notification.id) the Notification in Project
 * @param {*} notification
 */
 function setNotification(notification) {
    if (!data.notifications) {
        data.notifications = [];
    }
    var pos = -1;
    for (var i = 0; i < data.notifications.length; i++) {
        if (data.notifications[i].id === notification.id) {
            pos = i;
        }
    }
    if (pos >= 0) {
        data.notifications[pos] = notification;
    } else {
        data.notifications.push(notification);
    }
}

/**
 * Remove the Notification from Project
 * @param {*} notification
 */
function removeNotification(notification) {
    if (data.notifications) {
        for (var i = 0; i < data.notifications.length; i++) {
            if (data.notifications[i].id === notification.id) {
                data.notifications.splice(i, 1);
                return true;
            }
        }
    }
    return false;
}

/**
 * Set or add if not exist (check with script.id) the Script in Project
 * @param {*} script
 */
 function setScript(script) {
    if (!data.scripts) {
        data.scripts = [];
    }
    var pos = -1;
    for (var i = 0; i < data.scripts.length; i++) {
        if (data.scripts[i].id === script.id) {
            pos = i;
        }
    }
    if (pos >= 0) {
        data.scripts[pos] = script;
    } else {
        data.scripts.push(script);
    }
}

/**
 * Remove the Script from Project
 * @param {*} script
 */
 function removeScript(script) {
    if (data.scripts) {
        for (var i = 0; i < data.scripts.length; i++) {
            if (data.scripts[i].id === script.id) {
                data.scripts.splice(i, 1);
                return true;
            }
        }
    }
    return false;
}

/**
 * Set or add if not exist (check with report.id) the Report in Project
 * @param {*} report
 */
 function setReport(report) {
    if (!data.reports) {
        data.reports = [];
    }
    var pos = -1;
    for (var i = 0; i < data.reports.length; i++) {
        if (data.reports[i].id === report.id) {
            pos = i;
        }
    }
    if (pos >= 0) {
        data.reports[pos] = report;
    } else {
        data.reports.push(report);
    }
}

/**
 * Remove the Report from Project
 * @param {*} script
 */
 function removeReport(report) {
    if (data.reports) {
        for (var i = 0; i < data.reports.length; i++) {
            if (data.reports[i].id === report.id) {
                data.reports.splice(i, 1);
                return true;
            }
        }
    }
    return false;
}

/**
 * Set or add if not exist (check with location.id) the MapsLocation in Project
 * @param {*} location
 */
function setMapsLocation(location) {
    if (!data.mapsLocations) {
        data.mapsLocations = [];
    }
    var pos = -1;
    for (var i = 0; i < data.mapsLocations.length; i++) {
        if (data.mapsLocations[i].id === location.id) {
            pos = i;
        }
    }
    if (pos >= 0) {
        data.mapsLocations[pos] = location;
    } else {
        data.mapsLocations.push(location);
    }
}

/**
 * Remove the Maps Locations from Project
 * @param {*} location
 */
function removeMapsLocation(location) {
    if (data.mapsLocations) {
        for (var i = 0; i < data.mapsLocations.length; i++) {
            if (data.mapsLocations[i].id === location.id) {
                data.mapsLocations.splice(i, 1);
                return true;
            }
        }
    }
    return false;
}

/**
 * Get the project data in accordance with autorization
 */
function getProject(userId, userPermission, layoutId) {
    return new Promise(function (resolve, reject) {
        load(layoutId).then(()=>{
            const pdata = _filterProjectPermission(userPermission);
            resolve(pdata);
        });
    });
}

async function updateDevices(linkedDeviceTags, layoutId) {
    const devices = {};
    suposDevicesTagsObject = {};
    const drows = await prjstorage.getSection(prjstorage.TableType.SUPOSDEVICES);
    if (drows.length > 0) {
        for (var index = 0; index < drows.length; index++) {
            const value = JSON.parse(drows[index].value);
            if (value.tags && JSON.stringify(value.tags) !== '{}') {
                Object.values(value.tags).forEach((tag) => {
                    suposDevicesTagsObject[`${tag.id}/Page_${layoutId}`] = {
                        ...tag,
                        id: `${tag.id}/Page_${layoutId}`,
                        topic: {
                            ...value,
                            tags: {}
                        }
                    }
                });
            }
        }
    }

    linkedDeviceTags.forEach((item) => {
        const {
            topic: thisTopic,
            ...tag
        } = item;
        
        const id = `${item.id.split('/Page_')[0]}/Page_${layoutId}`;
        if (!suposDevicesTagsObject[id]) return;

        const { topic } = suposDevicesTagsObject[id];
        const topickId = topic.type === 'MQTTclient' ? `linked-mqtt/Page_${layoutId}` : `linked-rest-${topic.id}/Page_${layoutId}`;

        if (!devices[topickId]) {
            devices[topickId] = {
                ...topic,
                ...thisTopic,
                id: topickId,
                name: topic.type === 'MQTTclient' ? 'linked-mqtt' : thisTopic.name,
                enabled: true,
                tags: {}
            };
        }

        devices[topickId].tags[id] = {
            ...tag,
            id
        };

        if (topic.type === 'WebAPI') {
            devices[topickId].property.address = topic.property.address;
        }
    });

    return devices;
}

// 将绑定的supos设备保存到数据中
async function checkSuposDevices(prjcontent, layoutId) {
    // 已经绑定的tags
    const linkedDeviceTags= [];

    // 获取新的配置里的所有devicesTags
    const devicesTags = {};
    Object.values(prjcontent.devices).forEach((device) => {
        if (["0", "server"].includes(device.id)) return;
        Object.values(device.tags).forEach((tag) => {
            devicesTags[tag.id] = {
                ...tag,
                topic: {
                    ...device,
                    tags: {}
                }
            }
        });
    });

    prjcontent.hmi.views.forEach((view) => {
        if (!view.items || JSON.stringify(view.items) === '{}') {
            return;
        }
        Object.values(view.items).forEach((item) => {
            const {
                id,
                variableId,
                actions = [] 
            } = item?.property || {};

            if (actions?.length) {
                actions.forEach((action) => {
                    if (
                        action.variableId &&
                        action.variableId.startsWith('s_')
                    ) {
                        linkedDeviceTags.push({
                            ...devicesTags[action.variableId],
                            id: action.variableId
                        });
                    }
                });
            }

            // 直接绑定设备
            if (variableId && variableId.startsWith('s_')) {
                linkedDeviceTags.push({
                    ...devicesTags[variableId],
                    id: variableId
                });
                return;
            }

            // 图表类型，绑定的是图表
            if (id) {
                // 线形图
                if (prjcontent.charts?.length) {
                    prjcontent.charts.forEach((chart) => {
                        if (chart.id !== id || !chart.lines?.length) {
                            return;
                        }
                        chart.lines.filter(line => line.id.startsWith('s_')).forEach((line) => {
                            linkedDeviceTags.push({
                                ...devicesTags[line.id],
                                id: line.id
                            });
                        });
                    });
                }

                // 条形图
                if (prjcontent.graphs?.length) {
                    prjcontent.graphs.forEach((graph) => {
                        if (graph.id !== id || !graph.sources?.length) {
                            return;
                        }
                        graph.sources.filter(source => source.id.startsWith('s_')).forEach((source) => {
                            linkedDeviceTags.push({
                                ...devicesTags[source.id],
                                id: source.id
                            });
                        });
                    });
                }
            }
        });
    });

    const devices = await updateDevices(linkedDeviceTags, layoutId);

    for (let i in prjcontent.devices) {
        if (i.startsWith('linked-')) {
            delete prjcontent.devices[i];
        }
    }

    return {
        ...(prjcontent.devices || {}),
        ...devices
    };
}

function updateIds(prjcontent, layoutId) {
  const oldIds = [];
  Object.keys(prjcontent).forEach((key) => {
    switch (key) {
      case "devices":
        Object.keys(prjcontent[key]).forEach((deviceId) => {
          if (["0", "server"].includes(deviceId)) return;
          oldIds.push(deviceId);

          Object.keys(prjcontent[key][deviceId].tags).forEach((tagId) => {
            oldIds.push(tagId);
          });
        });
        break;
      case "hmi":
        prjcontent[key].views.forEach((view) => {
          oldIds.push(view.id);
          Object.values(view.items).forEach((item) => {
            oldIds.push(item.id);
            if (item?.property?.id) {
              oldIds.push(item.property.id);
            }
            if (item?.property?.variableId) {
              oldIds.push(item.property.variableId);
            }
          });
        });
        break;
      default:
        if (Array.isArray(prjcontent[key])) {
            prjcontent[key].forEach((item) => {
                oldIds.push(item.id);
              });
        }
        break;
    }
  });

  let newPrjcontent = JSON.stringify(prjcontent);

  [...new Set([...oldIds])].forEach((id) => {
    const initId = id.split('/Page_')[0];
    newPrjcontent = newPrjcontent.replaceAll(id, `${initId}/Page_${layoutId}`);
  });

  return JSON.parse(newPrjcontent);
}

/**
 * Set the new Project, clear all from database and add the new content
 * @param {*} prjcontent
 */
async function setProject(prjcontent, layoutId, isClear) {
    if (isClear) {
      prjcontent = updateIds(prjcontent, layoutId);
    }

    var devices = await checkSuposDevices(prjcontent, layoutId);
    return new Promise(function (resolve, reject) {
        try {
            var scs = [];
            Object.keys(prjcontent).forEach((key) => {
                if (key === 'devices') {
                    if (devices) {
                        Object.values(devices).forEach((device) => {
                            setDevice(device);
                            scs.push({
                                table: prjstorage.TableType.DEVICES,
                                name: device.id,
                                value: device,
                                layoutId: device.id === '0' ? '0' : layoutId
                            });
                        });
                    }
                } else if (key === 'hmi') {
                    // hmi
                    var hmi = prjcontent[key];
                    if (hmi) {
                        Object.keys(hmi).forEach((hk) => {
                            if (hk === 'views') {
                                // views
                                if (hmi[hk] && hmi[hk].length > 0) {
                                    for (var i = 0; i < hmi[hk].length; i++) {
                                        var view = hmi[hk][i];
                                        scs.push({ table: prjstorage.TableType.VIEWS, name: view.id, value: view, layoutId });
                                    }
                                }
                            } else {
                                // layout
                                const general = updateGeneralData(hk, hmi[hk]);
                                scs.push({ table: prjstorage.TableType.GENERAL, name: layoutId, value: general });
                            }
                        });
                    }
                } else if (key === 'server') {
                    // server
                    scs.push({ table: prjstorage.TableType.DEVICES, name: key, value: prjcontent[key], layoutId: 'server' });
                } else if (key === 'texts') {
                    // texts
                    var texts = prjcontent[key];
                    if (texts && texts.length) {
                        for (var i = 0; i < texts.length; i++) {
                            scs.push({ table: prjstorage.TableType.TEXTS, name: texts[i].name, value: texts[i], layoutId });
                        }
                    }
                } else if (key === 'alarms') {
                    // alarms
                    var alarms = prjcontent[key];
                    if (alarms && alarms.length) {
                        for (var i = 0; i < alarms.length; i++) {
                            scs.push({ table: prjstorage.TableType.ALARMS, name: alarms[i].name, value: alarms[i], layoutId });
                        }
                    }
                } else if (key === 'notifications') {
                    // notifications
                    var notifications = prjcontent[key];
                    if (notifications && notifications.length) {
                        for (var i = 0; i < notifications.length; i++) {
                            scs.push({ table: prjstorage.TableType.NOTIFICATIONS, name: notifications[i].id, value: notifications[i], layoutId });
                        }
                    }
                } else if (key === 'scripts') {
                    // scripts
                    var scripts = prjcontent[key];
                    if (scripts && scripts.length) {
                        for (var i = 0; i < scripts.length; i++) {
                            scs.push({ table: prjstorage.TableType.SCRIPTS, name: scripts[i].id, value: scripts[i], layoutId });
                        }
                    }
                } else if (key === 'reports') {
                    // reports
                    var reports = prjcontent[key];
                    if (reports && reports.length) {
                        for (var i = 0; i < reports.length; i++) {
                            scs.push({ table: prjstorage.TableType.REPORTS, name: reports[i].id, value: reports[i], layoutId });
                        }
                    }
                } else {
                    // charts, graphs, version
                    const general = updateGeneralData(key, prjcontent[key]);
                    scs.push({ table: prjstorage.TableType.GENERAL, name: layoutId, value: general });
                }
            });
            if (isClear) {
                deleteProject(layoutId).then(() => {
                    prjstorage.setSections(scs).then(() => {
                        logger.info(`project.prjstorage.set-project successfull!`, true);
                        resolve(prjcontent);
                    }).catch(function (err) {
                        // 保存失败，还原回去
                        setProject(data, layoutId, false);
                        reject(err);
                    });
                }).catch(function (err) {
                    // 清除失败，还原回去
                    setProject(data, layoutId, false);
                    reject(err);
                });
            } else {
                prjstorage.setSections(scs).then(() => {
                    logger.info(`project.prjstorage.set-project successfull!`, true);
                    resolve(true);
                }).catch(function (err) {
                    reject(err);
                });
            }
        } catch (err) {
            reject();
        }
    });
}

/**
 * Return Devices list
 */
function getDevices() {
    return data.devices;
}

/**
 * Return Device from name
 */
function getDevice(name) {
    return Object.values(data.devices).find(device => device.name === name);
}

/**
 * Get the device property
 */
function getDeviceProperty(query) {
    return new Promise(function (resolve, reject) {
        if (query.query === 'security') {
            prjstorage.getSection(prjstorage.TableType.DEVICESSECURITY, query.name, query.layoutId).then(drows => {
                if (drows.length > 0) {
                    resolve(drows[0]);
                } else {
                    resolve();
                }
            }).catch(function (err) {
                logger.error(`project.prjstorage.getdevice-property failed! '${prjstorage.TableType.DEVICESSECURITY} ${err}'`);
                reject(err);
            });
        } else {
            reject();
        }
    });
}

/**
 * Get the texts
 */
function getTexts(layoutId) {
    return new Promise(function (resolve, reject) {
        prjstorage.getSection(prjstorage.TableType.TEXTS, undefined, layoutId).then(drows => {
            if (drows.length > 0) {
                var texts = [];
                for (var id = 0; id < drows.length; id++) {
                    texts.push(JSON.parse(drows[id].value));
                }
                resolve(texts);
            } else {
                resolve();
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage.get-texts failed! '${prjstorage.TableType.TEXTS} ${err}'`);
            reject(err);
        });
    });
}

/**
 * Get the alarms
 */
function getAlarms(layoutId) {
    return new Promise(function (resolve, reject) {
        prjstorage.getSection(prjstorage.TableType.ALARMS, undefined, layoutId).then(drows => {
            if (drows.length > 0) {
                var alarms = [];
                for (var id = 0; id < drows.length; id++) {
                    alarms.push(JSON.parse(drows[id].value));
                }
                resolve(alarms);
            } else {
                resolve();
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage.get-alarms failed! '${prjstorage.TableType.ALARMS} ${err}'`);
            reject(err);
        });
    });
}

/**
 * Get the notifications
 */
 function getNotifications(layoutId) {
    return new Promise(function (resolve, reject) {
        prjstorage.getSection(prjstorage.TableType.NOTIFICATIONS, undefined, layoutId).then(drows => {
            if (drows.length > 0) {
                var notifications = [];
                for (var id = 0; id < drows.length; id++) {
                    notifications.push(JSON.parse(drows[id].value));
                }
                resolve(notifications);
            } else {
                resolve();
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage.get-notifications failed! '${prjstorage.TableType.NOTIFICATIONS} ${err}'`);
            reject(err);
        });
    });
}

/**
 * Get the scripts
 */
 function getScripts(layoutId) {
    return new Promise(function (resolve, reject) {
        prjstorage.getSection(prjstorage.TableType.SCRIPTS, undefined, layoutId).then(drows => {
            if (drows.length > 0) {
                var scripts = [];
                for (var id = 0; id < drows.length; id++) {
                    scripts.push(JSON.parse(drows[id].value));
                }
                resolve(scripts);
            } else {
                resolve();
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage.get-scripts failed! '${prjstorage.TableType.SCRIPTS} ${err}'`);
            reject(err);
        });
    });
}

/**
 * Get the reports
 */
 function getReports(layoutId) {
    return new Promise(function (resolve, reject) {
        prjstorage.getSection(prjstorage.TableType.REPORTS, undefined, layoutId).then(drows => {
            if (drows.length > 0) {
                var reports = [];
                for (var id = 0; id < drows.length; id++) {
                    reports.push(JSON.parse(drows[id].value));
                }
                resolve(reports);
            } else {
                resolve();
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage.get-reports failed! '${prjstorage.TableType.REPORTS} ${err}'`);
            reject(err);
        });
    });
}

/**
 * Get the Maps Locations
 */
function getMapsLocations() {
    return new Promise(function (resolve, reject) {
        prjstorage.getSection(prjstorage.TableType.LOCATIONS).then(drows => {
            if (drows.length > 0) {
                var locations = [];
                for (var id = 0; id < drows.length; id++) {
                    locations.push(JSON.parse(drows[id].value));
                }
                resolve(locations);
            } else {
                resolve();
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage.get-mapsLocations failed! '${prjstorage.TableType.LOCATIONS} ${err}'`);
            reject(err);
        });
    });
}

/**
 * Set the device property
 */
function setDeviceProperty(query) {
    return new Promise(function (resolve, reject) {
        if (query.query === 'security') {
            if (!query.value) {
                resolve();
                return;
            }
            prjstorage.setSection({ table: prjstorage.TableType.DEVICESSECURITY, name: query.name, value: query.value, layoutId: query.layoutId }).then(() => {
                resolve();
            }).catch(function (err) {
                logger.error(`project.prjstorage.setdevice-property failed! '${prjstorage.TableType.DEVICESSECURITY} ${err}'`);
                reject(err);
            });
        } else {
            reject();
        }
    });
}

/**
 * Batch set the device property
 */
function setDevicePropertys(query) {
    return new Promise(function (resolve, reject) {
        if (query.query === 'securitys') {
            if (!query.securitys || !query.securitys.length) {
                resolve();
                return;
            }

            const sections = [];
            query.securitys.forEach((item) => {
                if (!item.value) {
                    return;
                }
                sections.push({
                    table: prjstorage.TableType.DEVICESSECURITY,
                    name: item.id,
                    value: item.value,
                    layoutId: query.layoutId
                });
            });

            if(!sections.length) {
                resolve();
                return;
            }

            prjstorage.setSections(sections).then(() => {
                resolve();
            }).catch(function (err) {
                logger.error(`project.prjstorage.setdevice-property failed! '${prjstorage.TableType.DEVICESSECURITY} ${err}'`);
                reject(err);
            });
        } else {
            reject();
        }
    });
}

/**
 * 获取supos的设备
 */
function getSuposDevices({layoutId}) {
    return new Promise(function(resolve, reject) {
        var suposDevices = [];
        prjstorage.getSection(prjstorage.TableType.SUPOSDEVICES).then(drows => {
            if (drows.length > 0) {
                for (var index = 0; index < drows.length; index++) {
                    const value = JSON.parse(drows[index].value);
                    const tags = { ...value.tags };
                    value.tags = {};
                    Object.values(tags).forEach((item) => {
                        value.tags[`${item.id}/Page_${layoutId}`] = {
                            ...item,
                            id: `${item.id}/Page_${layoutId}`
                        };
                    });
                    suposDevices.push({
                      ...value,
                      id: `${value.id}/Page_${layoutId}`,
                    });
                }
                resolve(suposDevices);
            } else {
                resolve();
            }
        }).catch(function (err) {
            logger.error(`project.prjstorage.get--system-devices failed! '${prjstorage.TableType.SUPOSDEVICES} ${err}'`);
            reject(err);
        });
    });
}

function mqttFieldParams(params) {
    const {
        id,
        name,
        address,
        subs,
        memaddress
    } = params;

    return {
        "id": id,
        "name": name,
        "type": "json",
        "address": address,
        "options": {
            subs
        },
        "memaddress": memaddress,
        "daq": {
            "enabled": false,
            "interval": 60,
            "changed": false,
            "restored": false
        }
    }
}

function mqttParams(params) {
    const {
        parentId,
        topic,
        fields
    } = params;

    const self = `${parentId}-selfraw`;
    const tags = {
        [self]: {
            "id": self,
            "name": topic,
            "type": "raw",
            "address": topic,
            "options": {
                subs: []
            },
            "daq": {
                "enabled": false,
                "interval": 60,
                "changed": false,
                "restored": false
            }
        }
    }

    // 添加具体字段
    if (fields.length) {
        const subs = fields.map(field => field.name);
        fields.forEach((field) => {
            const fieldId = `${parentId}-${field.name}`;
            const filedParams = mqttFieldParams({
                id: fieldId,
                name: field.displayName ? `${field.displayName}(${field.name})` : field.name,
                address: topic,
                memaddress: field.name,
                subs
            });

            tags[fieldId] = filedParams;
        });
    }

    return {
        "type": "MQTTclient",
        "property": {
            "address": "mqtt://emqx:1883",
            "port": null,
            "slot": null,
            "rack": null,
            "baudrate": 9600,
            "databits": 8,
            "stopbits": 1,
            "parity": "None",
            "delay": 10,
            "socketReuse": null
        },
        tags
    };
}

function restApiParams(params) {
    const {
        parentId,
        alias,
        fields,
        topic
    } = params;


    const tags = {};

    const self = `${parentId}-selfraw`;
    tags[self] = {
        "id": self,
        "name": topic,
        "label": topic,
        "type": "string",
        "address": alias,
        "daq": {
            "enabled": false,
            "interval": 60,
            "changed": false,
            "restored": false
        }
    }

    if (fields.length) {
        fields.forEach((field) => {
            const fieldId = `${parentId}-${field.name}`;
            tags[fieldId] = {
                "id": fieldId,
                "name": field.displayName ? `${field.displayName}(${field.name})` : field.name,
                "label": field.name,
                "type": field.type || "string",
                "address": `${alias}:[]:${field.name}`,
                "daq": {
                    "enabled": false,
                    "interval": 60,
                    "changed": false,
                    "restored": false
                }
            }
        })
    }

    return {
        "type": "WebAPI",
        "property": {
            "address": `http://hasura:8080/api/rest/${alias}/0/10`,
            "port": null,
            "slot": null,
            "rack": null,
            "baudrate": 9600,
            "databits": 8,
            "stopbits": 1,
            "parity": "None",
            "method": "GET",
            "format": "JSON",
            "delay": 10,
            "socketReuse": null,
            "header": [{
                "key": "apikey",
                "value": "4174348a-9222-4e81-b33e-5d72d2fd7f1e"
            }]
        },
        tags
    }
}

/**
 * 设置supos的设备
 */
function setSuposDevices(data) {
    const sections = [];
    data.forEach((item) => {
        const {
            topic,
            type,
            alias
        } = item;
        const id = `s_${alias}`;
        const config = item.config ? JSON.parse(item.config) : {};
        const fields = item.fields ? JSON.parse(item.fields) : [];
        const params = mqttParams({
            ...item,
            parentId: id,
            config,
            fields
        });

        sections.push({
            table: prjstorage.TableType.SUPOSDEVICES,
            name: id,
            value: {
                id,
                name: `system-${topic}`,
                polling: 1000,
                ...params
            }
        });

        // 关系型数据，增加webAPI
        if (type === 2) {
            const webapiId = `${id}-webapi`;
            const webapiParams = restApiParams({
                ...item,
                parentId: webapiId,
                fields
            });
            sections.push({
                table: prjstorage.TableType.SUPOSDEVICES,
                name: webapiId,
                value: {
                    id: webapiId,
                    name: `system-webapi-${topic}`,
                    polling: 1000,
                    ...webapiParams
                }
            });
        }

    });

    return new Promise(function (resolve, reject) {
        prjstorage.setSections(sections).then(() => {
            resolve();
        }).catch(function (err) {
            logger.error(`project.prjstorage.setdevice-property failed! '${prjstorage.TableType.DEVICESSECURITY} ${err}'`);
            reject(err);
        });
    });
}

/**
 * 删除supos的mqtt和restApi
 */
function deleteSuposDevices(data) {
    const sections = [];
    data.forEach((item) => {
        const {
            topic,
            config,
        } = item;

        const id = `s_${topic.replaceAll('/','-')}`;

        sections.push({
            table: prjstorage.TableType.SUPOSDEVICES,
            name: id
        });
    })

    return new Promise(function (resolve, reject) {
        prjstorage.deleteSuposDevices(sections).then(() => {
            resolve();
        }).catch(function (err) {
            logger.error(`project.prjstorage.setdevice-property failed! '${prjstorage.TableType.DEVICESSECURITY} ${err}'`);
            reject(err);
        });
    });
}

/**
 * 删除dashboard
 */
function deleteProject(layoutId) {
    const deleteTables = ['views', 'devices', 'devicesSecurity', 'texts', 'alarms', 'notifications', 'scripts', 'reports'];
    const sections = deleteTables.map((table) => {
        return {
            table,
            layoutId
        }
    });

    return new Promise(function (resolve, reject) {
        prjstorage.deleteProject(sections).then(() => {
            resolve();
        }).catch(function (err) {
            logger.error(`project.prjstorage.setdevice-property failed! '${prjstorage.TableType.DEVICESSECURITY} ${err}'`);
            reject(err);
        });
    });
}

/**
 * Return Project demo from file
 */
function getProjectDemo() {
    var demoProject = path.join(settings.appDir, 'project.demo.fuxap');
    return JSON.parse(fs.readFileSync(demoProject, 'utf8'));;
}

function _filterProjectPermission(userPermission) {
    var result = JSON.parse(JSON.stringify(data));// = { devices: {}, hmi: { views: [] } };
    const projectPermission = runtime.checkPermission(userPermission, false);
    if (!projectPermission.show || !projectPermission.enabled) {   // is admin or secure disabled
        // from device remove the not used (no permission)
        // delete result.devices;
        delete result.server;
        // check navigation permission
        if (result.hmi.layout && result.hmi.layout.navigation.items) {
            for (var i = result.hmi.layout.navigation.items.length - 1; i >= 0; i--) {
                const itemPermission = runtime.checkPermission(userPermission, result.hmi.layout.navigation.items[i]);
                if (!itemPermission.enabled) {
                    result.hmi.layout.navigation.items.splice(i, 1);
                }
            }
        }
        // check header permission
        if (result.hmi.layout && result.hmi.layout.header.items) {
            for (var i = result.hmi.layout.header.items.length - 1; i >= 0; i--) {
                const itemPermission = runtime.checkPermission(userPermission, result.hmi.layout.header.items[i].property, true);
                if (!itemPermission.enabled || !itemPermission.show) {
                    result.hmi.layout.header.items.splice(i, 1);
                }
            }
        }
        // check view item permission show / enabled
        for (var i = 0; i < result.hmi.views.length; i++) {
            var view = result.hmi.views[i];
            if (result.hmi.views[i].items) {
                Object.values(result.hmi.views[i].items).forEach((item) => {
                    if (item.property) {
                        const itemPermission = runtime.checkPermission(userPermission, item.property, false, true);
                        if (!itemPermission.show) {
                            var position = view.svgcontent.indexOf(item.id);
                            if (position >= 0) {
                                position += item.id.length + 1;
                                var hidetext = ' visibility="hidden" ';
                                view.svgcontent = view.svgcontent.slice(0, position) + hidetext + view.svgcontent.slice(position);
                            }
                        } else if (!itemPermission.enabled) {
                            item.property.events = [];
                            // disable the html controls (select, input, button)
                            const indexInContent = view.svgcontent.indexOf(item.id);
                            if (indexInContent >= 0) {
                                var splitted = utils.domStringSplitter(view.svgcontent, 'foreignobject', indexInContent);
                                if (splitted.tagcontent && splitted.tagcontent.length) {
                                    var disabled = utils.domStringSetAttribute(splitted.tagcontent, ['select', 'input', 'button'], 'disabled');
                                    // disabled = utils.domStringSetOverlay(disabled, ['ngx-switch']);
                                    view.svgcontent = splitted.before + disabled + splitted.after;
                                }
                            }
                        }
                    }
                });
            }
        }
    }
    return result;
}

function _mergeDefaultConfig() {
    return new Promise(async function (resolve, reject) {
        try {
            if (process.env.DEVICES && typeof process.env.DEVICES === 'string') {
                try {
                    logger.info('project.merge-config: in progress!');
                    var devices = JSON.parse(process.env.DEVICES);
                    devices.forEach(device => {
                        try {
                            // check device required
                            if (!device || !device.id || !device.name || !device.type || !device.configs) {
                                logger.error(`project.merge-config: DEVICES${JSON.stringify(device)} missing property!`);
                            } else {
                                var existDevice = data.devices[device.id];
                                var deviceToAdd = new Device(device);
                                if (existDevice) {
                                    deviceToAdd.tags = existDevice.tags;
                                }
                                setDevice(deviceToAdd, true);
                                logger.info(`project.merge-config: Device ${deviceToAdd.name} added!`);
                            }
                        } catch (err) {
                            logger.error(`project.merge-config: DEVICES${JSON.stringify(device)} failed! ${err}`);
                            reject();
                        }
                    });
                } catch (err) {
                    logger.error(`project.merge-config: DEVICES failed! ${err}`);
                }
            }
            resolve();
        } catch (err) {
            logger.error(`project.merge-config: failed! ${err}`);
            reject();
        }
    });

    function Device(device, tags) {
        this.id = device.id;
        this.name = device.name;
        this.enabled = true;
        this.type = device.type;
        this.polling = 1000 || device.configs.requestIntervalMs;
        this.tags = tags || {};
        this.property = device.configs;

        var a = Object.values(DeviceType);
        if (Object.values(DeviceType).indexOf(device.type) === -1) {
            throw new Error('DeviceType unknow');
        }
    }
}

const ProjectDataCmdType = {
    SetDevice: 'set-device',
    SetDevices: 'set-devices',
    UpdateDevice: 'update-device',
    DelDevice: 'del-device',
    SetView: 'set-view',
    DelView: 'del-view',
    HmiLayout: 'layout',
    Charts: 'charts',
    Graphs: 'graphs',
    Languages: 'languages',
    ClientAccess: 'client-access',
    SetText: 'set-text',
    SetText: 'set-text',
    DelText: 'del-text',
    SetAlarm: 'set-alarm',
    DelAlarm: 'del-alarm',
    SetNotification: 'set-notification',
    DelNotification: 'del-notification',
    SetScript: 'set-script',
    DelScript: 'del-script',
    SetReport: 'set-report',
    DelReport: 'del-report',
    SetMapsLocation:'set-maps-location',
    DelMapsLocation: 'del-maps-location',
}

module.exports = {
    init: init,
    load: load,
    getDevices: getDevices,
    getDevice: getDevice,
    getAlarms: getAlarms,
    getNotifications: getNotifications,
    getScripts: getScripts,
    getReports: getReports,
    getDeviceProperty: getDeviceProperty,
    setDeviceProperty: setDeviceProperty,
    setDevicePropertys: setDevicePropertys,
    getSuposDevices: getSuposDevices,
    setSuposDevices: setSuposDevices,
    deleteSuposDevices: deleteSuposDevices,
    setProjectData: setProjectData,
    getProject: getProject,
    setProject: setProject,
    getProjectDemo: getProjectDemo,
    deleteProject: deleteProject,
    ProjectDataCmdType, ProjectDataCmdType,
};
